package org.gfix.binding;

import java.util.LinkedList;
import java.util.List;

import org.gfix.exception.FixException;
import org.gfix.parser.FixErrorHandler;
import org.gfix.parser.FixHandler;
import org.gfix.parser.FixParseFault;
import org.gfix.parser.FixParser;
import org.gfix.parser.MessageSource;
import org.gfix.types.FixVersion;

/**
 * This class is responsible for the core Message to object bindings
 * @author alan
 *
 */
public class FixBinder implements FixHandler
{

	private BindingCache bindingCache;
	private FixParser parser;
	
	
	private List<FixEvent> currentEvents = new LinkedList<FixEvent>(); 
	private TypeSelector typeSelector = new TypeSelector();
	private FixBindingMetaData currentMessageFixBinding;
	private FixBindingContext currentContext;
	private FixErrorHandler errorDelegate;
	
	public FixBinder(FixParser parser)
	{
		this(parser, null);
	}
	
	
	public FixBinder(FixParser parser, FixErrorHandler handler)
	{
		this(parser, handler,new BindingCache());
	}
	
	public FixBinder(FixParser parser, FixErrorHandler handler, BindingCache bindingCache) {
		super();
		this.bindingCache = bindingCache;
		this.parser = parser;
		this.errorDelegate = handler;
	}

	public void register(Class<?> fixbound)throws FixBindingException
	{
		ClassFixBindingDescription description =  
								new ClassFixBindingDescription(fixbound);
		bindingCache.addFixBinding(fixbound, description);
		// need to also set this up for inbound messages.
		
	}
		
	public void write(MessageSink sink, Object message, FixVersion version) throws FixBindingException
	{	
		if(sink == null)
		{
			throw new FixBindingException("MessageSink cannot be null");
		}
		
		ClassFixBindingDescription description = bindingCache.getFixBinding(message.getClass());
		if(description == null)
		{
			description = new ClassFixBindingDescription(message.getClass());
			bindingCache.addFixBinding(message.getClass(), description);
		}
	
		description.writeTo(sink,message,version);		
	}
	
	
	public Object read(MessageSource source) throws FixBindingException, FixException
	{
		this.currentMessageFixBinding = null;
		typeSelector.reset(bindingCache.getAllBindings());
		parser.parse(source, this);
		return getCurrentMessageObject();
	}
	
	Object getCurrentMessageObject()
	{
		if(this.currentContext != null)
		{
			return this.currentContext.getBoundObject();
		}
		return null;
	}
	
	
	@Override
	public void newTag(int tag, String contents) throws FixException
	{
		if(currentMessageFixBinding == null)
		{
			ClassFixBindingDescription classDesc = typeSelector.newTag(tag,contents,currentEvents);
			if(classDesc == null)
			{
				this.currentEvents.add(new FixEvent(tag,contents));
			}
			else
			{
				currentMessageFixBinding  = classDesc.getMetadata(typeSelector.getSelectedVersion());
				currentContext = new FixBindingContext();
				currentContext.setBoundObject(classDesc.createBoundObject());
				
				for(FixEvent fe : this.currentEvents)
				{
					currentMessageFixBinding.setValue(fe.tag, fe.content, currentContext);
				}
				this.currentEvents.clear();
				currentMessageFixBinding.setValue(tag, contents, currentContext);
			}
		}	
		else
		{
			currentMessageFixBinding.setValue(tag, contents, currentContext);
		}
	}


	@Override
	public void warning(FixParseFault fault) {
		if(this.errorDelegate != null)
		{
			this.errorDelegate.warning(fault);
		}
	}
	
	@Override
	public void error(FixParseFault fault) {
		if(this.errorDelegate != null)
		{
			this.errorDelegate.error(fault);
		}
	}
}

class FixEvent
{
	int tag;
	String content;
	
	FixEvent(int tag, String content) {
		super();
		this.tag = tag;
		this.content = content;
	}	
}
